Read Buf

Read Buf

Netflix 的快速事件通知系统

arch

Netflix 拥有超过 2.2 亿名活跃用户,他们在每次使用过程中会执行许多操作,比如重命名个人资料或者观看某个节目。为了保持跨设备的一致使用体验,及时响应这些操作非常重要。这项任务并不容易,因为支持的设备种类繁多,用户操作也非常频繁。为了解决这个问题,我们开发了一个快速事件通知系统 (Rapid Event Notification System,RENO),能够以可扩展的方式支持服务器发起与设备通信的需求。

在这篇博文中,我们将概述 Netflix 的快速事件通知系统,并分享我们在此过程中获得的一些经验。

动 机

随着 Netflix 用户数量的快速增长和系统复杂性的增加,我们的架构已经演变为一种支持在线和离线计算的异步模式。要在各种平台 (iOS、Android、智能电视、Roku、Amazon FireStick、网络浏览器) 和设备类型 (手机、平板电脑、电视、电脑、机顶盒) 上提供无缝一致的 Netflix 体验,需要超越传统的请求-响应模型。随着时间的推移,我们发现越来越多的用例需要后台系统主动与设备通信,以快速且一致地通知用户驱动的更改或体验更新。

用 例

  • 观看活动
    当用户开始观看节目时,他们的“继续观看”列表应在所有设备上更新。
  • 个性化体验刷新
    Netflix 推荐引擎持续为每个用户刷新推荐内容,需要及时将更新的内容传递到设备。
  • 会员计划更改
    用户更改他们的计划类型,这种变化需要立即在所有设备上反映。
  • 用户“我的列表”更新
    当用户更新“我的列表”时,所有设备上都应同步这些更改。
  • 用户个人资料更改
    用户更新帐户设置(如添加/删除/重命名个人资料或更改内容首选成熟度级别)时,所有设备都应同步更新。
  • 系统诊断信号
    在某些情况下,我们需要向设备上的 Netflix 应用程序发送诊断信号,帮助排除故障并启用跟踪功能。

设计决策

在设计系统时,我们做出了一些关键决策,从而形成了 RENO 的架构:

  1. 单一事件源
  2. 事件优先级设置
  3. 混合通信模型
  4. 目标传递
  5. 管理高 RPS

单一事件源

我们希望支持的用例源自各种内部系统和用户操作,因此我们需要监听多个微服务的事件。在 Netflix,近实时事件流由内部分布式计算框架 Manhattan 管理(详细信息请见这里)。我们利用 Manhattan 的事件管理框架创建了一个中央事件源,作为 RENO 的单一事件来源。

事件优先级设置

由于用例在来源和重要性方面差别很大,我们在事件处理过程中进行了分级。例如,用户触发的“更改个人资料成熟度级别”事件优先级应高于“系统诊断信号”。因此,我们为每个用例分配了优先级,通过路由到特定优先级的队列和相应的处理集群来分割事件流量。这种分离使我们能够独立调整系统配置和扩展策略,以适应不同的事件优先级和流量模式。

混合通信模型

如前所述,支持多个平台是 RENO 的一大挑战。虽然移动设备几乎总是连接到互联网,但智能电视通常只有在使用时才在线。这种网络连接的多样性使得选择单一的传递模型变得困难。例如,完全依赖 Pull 模型(即设备频繁请求服务器获取更新)会导致移动应用频繁通信,触发 iOS 和 Android 平台的通信限制(同时也需要考虑低带宽连接)。另一方面,仅使用 Push 机制会导致智能电视在关闭期间错过通知。因此,我们选择了混合 Push 和 Pull 通信模型,服务器尝试使用 Push 通知立即传递通知,而设备在应用程序生命周期的各个阶段联系服务器。

这种 Push 和 Pull 结合的传递模型还支持仅限于单一通信模型的设备,包括不支持 Push 通知的旧设备。

目标传递

考虑到用例在来源和目标设备类型上的多样性,我们构建了设备特定的通知传递功能。通过这个功能,我们可以根据用例需求,向特定设备类别发送通知。当接收到一个可操作事件时,RENO 会应用特定业务逻辑,收集有资格接收该通知的设备列表并尝试传递。这大大减少了外发流量。

管理高 RPS

考虑到 Netflix 拥有超过 2.2 亿用户,在观看会话期间每个用户可能产生大量事件,在高峰时段,RENO 每秒处理约 15 万个事件。这种高 RPS 可能会造成“雷鸣般的羊群问题”(thundering herd problem),给内部和外部下游服务带来压力。为此,我们实施了一些优化措施:

  • 事件年龄
    许多需通知给设备的事件是时间敏感的,如不及时发送,价值会大打折扣。我们通过陈旧性过滤器筛选旧事件,如果事件年龄超过可配置阈值就不处理。这样可以在处理过程早期过滤掉无价值的事件,防止队列因上游积压的事件而被淹没。
  • 在线设备
    为了减少持续的流量,通知只发送给在线设备,利用 Zuul 保持最新的注册表来实现(详细信息请见这里)。
  • 扩展策略
    为解决雷鸣般的羊群问题并保持延迟可控,我们配置了比缩减策略更激进的扩展策略。这种方法使得计算资源能在队列增长时快速跟上。
  • 事件去重
    iOS 和 Android 平台严格限制后台应用活动水平,因此 RENO 对传入事件进行了去重。在高 RPS 情况下,可能会发生重复事件,我们会在不丢失上下文的情况下合并这些事件。
  • 隔离传递
    使用多个下游服务向不同设备平台发送推送通知,包括 Apple 的 APNS 和 Google 的 FCM 等外部服务。为防止某个下游服务的故障影响整个通知服务,我们在不同平台之间并行传递事件。如果某个下游服务或平台无法传递通知,其他设备不受影响,仍能接收通知。

架 构

架构图

如图所示,RENO 服务可以分为以下几个组件:

事件触发器

需要刷新用户设备体验的用户操作和系统驱动的更新。

事件管理引擎

Netflix 的近实时事件流管理框架 Manhattan 可以配置为监听特定事件并将事件转发到不同的队列。

基于事件优先级的队列

Amazon SQS 队列由基于优先级的事件转发规则填充,这些规则在 Manhattan 中设置,以允许基于优先级的流量分片。

基于事件优先级的集群

AWS 实例集群订阅相应优先级的队列,处理队列中所有到达的事件并生成可操作通知。

外发消息系统

Netflix 消息系统用于向用户发送应用内推送通知,以将 RENO 生成的通知发送到移动设备。该消息系统在这篇博文中有描述。

对于网络、电视和其他流媒体设备的通知,我们使用自制的推送通知解决方案 Zuul Push,提供“始终在线”的持久连接。详细了解 Zuul Push 解决方案,请听取 Netflix 同事的此演讲

持久存储

Cassandra 数据库存储 RENO 为每个设备发出的所有通知,允许这些设备以自己的节奏轮询消息。

可观测性

在 Netflix,我们非常重视系统中的强大监控,以提供清晰的系统健康状况视图。对于像 RENO 这样依赖多个上游系统的高 RPS 服务,必须有强大的指标、警报和日志记录组合。除了标准的系统健康指标(如 CPU、内存和性能)外,我们还添加了许多“服务边缘”指标和日志记录,以捕捉上游或下游系统的异常。此外,我们为重要指标添加了趋势分析,以帮助抓住长期退化现象。我们使用名为 Mantis 的实时流处理应用对 RENO 进行检测(详细信息请见这里)。它使我们能在设备特定粒度上实时跟踪事件,从而更轻松地进行调试。最后,我们发现平台特定的警报(如 iOS、Android 等)有助于更快地找到问题根源。

成 果

  • 轻松支持新用例
  • 随着吞吐量增加而水平扩展

最初构建 RENO 时,我们的目标仅限于实现产品的“个性化体验刷新”用例。随着 RENO 设计的不断发展,支持新用例变得可能,RENO 很快被定位为 Netflix 各产品领域的集中快速通知服务。

早期的设计决策取得了成功,比如将新用例添加实现为“即插即用”解决方案,并提供跨所有平台的混合传递模型。我们能够快速添加其他产品用例,从而推动了大量创新。

一个重要的经验是确保 RENO 能够水平扩展,以应对更多事件类型和更高吞吐量。这主要通过允许基于事件类型或优先级进行分片,以及使用能通过增加处理机器来扩展的异步事件驱动处理模型实现。

展望未来

随着 Netflix 用户群的持续快速增长,像 RENO 这样的服务变得越来越重要,帮助用户获得最佳和最新的 Netflix 体验。从会员更新到上下文个性化,我们不断发展通知方案,以继续改善用户体验。从架构上,我们正在评估内置更多功能的机会,例如保证消息传递和消息批处理,这将打开更多用例并减少 RENO 的通信负担。

共同构建伟大事业

我们刚刚开始这段构建有影响力的系统的旅程,这些系统有助于推动我们的业务向前发展。将这些工程解决方案带入现实的核心是与同事直接合作,并使用最具影响力的工具和技术。如果这让你感到兴奋,我们希望你能加入我们